/*
 * Project CHEN Rendering Engine : /film.h
 * Created: 2021-01-29, Last modified:  2021-01-29
 * 
 * This header includes classes about the final imaging pipeline.
 * 
 * Copyright (C) 2021 Hineven (hineven@pku.edu.cn)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifndef _H_FILM
#define _H_FILM
#include "geometry.hpp"
#include "spectrum.hpp"
namespace chen {

// The r span of a filter is the maximum nonzero value coordinate value.
// Or we say it is the radius of a 2 dimension function
class Filter {
public:
    Float r;
    inline Filter (Float r): r(r) {}
    virtual Float f (Float dx, Float dy) const = 0;
} ;

// The gaussian filter
class GaussianFilter : public Filter {
private:
    Float B;
public:
    Float a;
    inline GaussianFilter (Float r, Float a):
        Filter::Filter(r), a(a) {
            B = exp(-a*r*r);
            assert(a > 0);
        }
    inline Float f (Float dw, Float dh) const {
        if(dw*dw+dh*dh > r*r) return 0;
        return (Float)(exp(-a*(dw*dw+dh*dh))-B);
    }
} ;

class Sample {
public:
    Point2 p;
    Spectrum s;
    inline Sample () {}
    inline Sample (const Point2 & p, const Spectrum & s):
        p(p), s(s) {}
} ;

class Film {
public:
    pthread_mutex_t submitting;
    int resolution_w, resolution_h;
    // pixel width described in meters
    // only makes sense in realistic cameras
    Float ppm;
    Vector3 * image;
    Float * sample_counts;
    const Filter * filter;
    Float sensitivity;
    //ppmv: value of pixel per meter (the width of a pixel)
    inline Film (int rw, int rh, const Filter * fltr, Float sens, Float ppmv = 0) {
        resolution_w = rw;
        resolution_h = rh;
        ppm = ppmv;
        filter = fltr;
        sensitivity = sens;
        image = new Vector3[rw*rh];
        std::fill(image, image+rw*rh, Vector3(0));
        sample_counts = new Float[rw*rh];
        memset(sample_counts, 0, sizeof(Float)*rw*rh);
        pthread_mutex_init(&submitting, nullptr);
    }
    inline ~Film () {
        delete [] image;
        delete [] sample_counts;
        pthread_mutex_destroy(&submitting);
    }
    inline void submit (const Sample * samples, int nsamples) {
        pthread_mutex_lock(&submitting);
        for(int i = 0; i<nsamples; i++) {
            int w0 = floor(samples[i].p.x - filter->r);
            int w1 = ceil(samples[i].p.x + filter->r);
            int h0 = floor(samples[i].p.y - filter->r);
            int h1 = ceil(samples[i].p.y + filter->r);
            for(int dw = max(0, w0); dw <= min(resolution_w-1, w1); dw++)
                for(int dh = max(0, h0); dh <= min(resolution_h-1, h1); dh++) {
                    Float f = filter->f(
                            dw+0.5-samples[i].p.x,
                            dh+0.5-samples[i].p.y
                        );
                    image[dw+dh*resolution_w] += 
                        samples[i].s.RGB()*f;
                    sample_counts[dw+dh*resolution_w] += f;
                }
        }
        pthread_mutex_unlock(&submitting);
    }
    inline void clear () {
        pthread_mutex_lock(&submitting);
        std::fill(image, image+resolution_w*resolution_h, Vector3(0));
        memset(sample_counts, 0, sizeof(Float)*resolution_w*resolution_h);
        pthread_mutex_unlock(&submitting);
    }
    inline void block () {
        pthread_mutex_lock(&submitting);
    }
    inline void release () {
        pthread_mutex_unlock(&submitting);
    }
    inline Vector3 RGB (int w, int h) const {
        if(sample_counts[w+h*resolution_w] == 0) return Vector3(0);
        return image[w+h*resolution_w]*sensitivity/sample_counts[w+h*resolution_w];
    }
} ;

}
#endif